自已项目中需要使用一个类此为 iTunes 中音乐评星的功能. 在 gihub 中找了一些发现都太繁琐. 本着一切从简, 能自己动手就自己动手的原则, 就参照开源的实现自己写了一个.
先来看看图片中我们要实现的控件.
这个 StarRatingView
有两种状态, 分别是可编辑和不可编辑的状态. UI结构是一个 baseView 中拥有 5 个 UIImageView
, 根据用户手指的移动 CGPoint
, 为相应的 UIImageView
填充高亮或普通的 star 图片.
首先, 我们创建一个 StarRatingView
的类, 它继承自 UIView.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class StarRatingView: UIView { private var notSelectedImage: UIImage = UIImage(named: "star_empty")! { didSet { refresh() } } private var halfSeletedImage: UIImage = UIImage(named: "star_full")! { didSet { refresh() } } @IBInspectable var editable: Bool = true @IBInspectable var rating: Int = 0 { didSet { refresh() } } private var imageViews = [UIImageView]() }
|
接着我们定义一个maxRating
属性, 它用来表示最大星星数量, 默认值是 5. 在它的 didSet
方法中, 我们进行 UIImageView 的初始化, 创建相应个数的 UIImageView.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var maxRating: Int = 5 { didSet { imageViews.forEach { $0.removeFromSuperview() } imageViews.removeAll() for _ in 0..<maxRating { let imageView = UIImageView() imageView.contentMode = .scaleAspectFit imageViews.append(imageView) addSubview(imageView) } setNeedsLayout() refresh() } }
|
之后我们来定义上面使用到的 refresh
方法, 它的逻辑非常简单, 就是遍历 imageViews 数组, 填充相应的图片.
1 2 3 4 5 6 7 8 9 10 11
| private func refresh() { imageViews.enumerated().forEach { index, imageView in if self.rating >= index + 1 { imageView.image = fullSelectedImage } else { imageView.image = notSelectedImage } } }
|
接着, 来处理 StarRatingView
的布局. 我们重写 layoutSubviews
的方法. 遍历 imageViews 数组, 为其设置计算好的 frame.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| private var midMargin: CGFloat = 0 private var leftMargin: CGFloat = 0 private var minImageSize: CGSize = CGSize.zero override func layoutSubviews() { super.layoutSubviews() var desiredImageWidth: CGFloat = 0 if editable { desiredImageWidth = (self.frame.size.width - (self.leftMargin * 2) - (self.midMargin * CGFloat(self.imageViews.count))) / CGFloat(self.imageViews.count) } else { desiredImageWidth = self.frame.size.width / 5 } let imageWidth = max(self.minImageSize.width, desiredImageWidth) let imageHeight = max(self.minImageSize.height, self.frame.size.height) imageViews.enumerated().forEach { index, imageView in imageView.frame = CGRect(x: self.leftMargin + CGFloat(index) * (self.midMargin + imageWidth), y: 0, width: imageWidth, height: imageHeight) } }
|
最后我们来处理用户手指的触摸动作. 我们定义一个 private 的方法 handleTouch(location: CGPoint)
它接受一个用户点击的坐标. 以此来计算需要显示多少个 star.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func handleTouch(location: CGPoint) { if !self.editable { return } var newRating = 0 for (index, imageView) in imageViews.enumerated().reversed() { if location.x > imageView.frame.origin.x { newRating = index + 1 break } } self.rating = newRating }
|
这样我们就可以在 UIView 的响应触摸事件的方法中调用 handleTouch(location: CGPoint)
了.
1 2 3 4 5 6 7 8 9 10 11
| override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { let touch = touches.first! let location = touch.location(in: self) handleTouch(location: location) } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { let touch = touches.first! let location = touch.location(in: self) handleTouch(location: location) }
|
OK! Done.
在外部使用的时候, 我们可以指定其最大的 star 数量, 也可以通过 ratring
属性来控制当前高亮的 star 数量, 你也可以非常方便的更换 star 图片.